package memoize;

import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import cashe.Computable;

public class Memoizer<A, V> implements Computable<A, V> {
	private final Computable<A, V> computer;
	private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

	public Memoizer(Computable<A, V> computer) {
		super();
		this.computer = computer;
	}

	@Override
	public synchronized V compute(final A arg) throws InterruptedException {
		Future<V> f = cache.get(arg);
		if (f == null) {
			Callable<V> eval = new Callable<V>() {
				@Override
				public V call() throws Exception {
					return computer.compute(arg);
				}
			};
			FutureTask<V> ft = new FutureTask<>(eval);
			f = cache.putIfAbsent(arg, ft);
			if (f == null) {
				f = ft;
				ft.run();
			}
		}
		try {
			return f.get();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// main is just to test
	public static void main(String[] args) throws InterruptedException {
		Memoizer<String, BigInteger> memoizer = new Memoizer<>(
				new ExpensiveComputation());
		System.out.println(memoizer.compute("12")); // long
		System.out.println(memoizer.compute("23")); // long
		System.out.println(memoizer.compute("12")); // quick
		System.out.println(memoizer.compute("23")); // quick
		System.out.println(memoizer.compute("34")); // long

	}
}

class ExpensiveComputation implements Computable<String, BigInteger> {
	@Override
	public BigInteger compute(String arg) throws InterruptedException {
		Thread.sleep(1000);
		return new BigInteger(arg);
	}

}
